package auction.securechannel;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; 
import java.security.PublicKey; 
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.apache.log4j.Logger;


public class RSAChannel extends ChannelDecorator{
	public static Logger log = Logger.getLogger(RSAChannel.class);

	private Cipher crypt ;

	public RSAChannel(Channel decoratedChannel) throws NoSuchAlgorithmException, NoSuchPaddingException {
		super(decoratedChannel);
		Security.addProvider(new BouncyCastleProvider());
		crypt = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding"); 
	}

	@Override
	public void send(byte[]  message)  {
		byte[]originMessage=message;
		try {
			String temp = new String (message);
			if (!(temp.startsWith("!list") || temp.startsWith("!end") || temp.startsWith("!exit")))
				message = encrypt(message);
		} catch (InvalidKeyException e) {
			log.debug("Error: Invalid Key");
			message=originMessage;
		} catch (NoSuchAlgorithmException e) {
			log.debug("Error: NoSuchAlgorithmException");
			message=originMessage;
		} catch (NoSuchPaddingException e) {
			log.debug("Error:NoSuchPaddingException");
			message=originMessage;
		} catch (IllegalBlockSizeException e) {
			log.debug("Error:IllegalBlockSizeException");
			message=originMessage;
		} catch (BadPaddingException e) {
			log.debug("Error: BadPaddingException");
			message=originMessage;
		} catch (UnsupportedEncodingException e) {
			log.debug("Error: UnsupportedEncodingException");
			message=originMessage;
		}
		decoratedChannel.send(message);

	}

	private byte[]  encrypt(byte[]  message) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {

		// MODE is the encryption/decryption mode 
		// KEY is either a private, public or secret key 
		// IV is an init vector, needed for AES 

		if (message!=null){
			crypt = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding"); 
			crypt.init(Cipher.ENCRYPT_MODE, this.getPublicKey());

			byte [] plain = new byte[100]; // initialize to all zero bytes


			plain = message;


			// First encrypt: length of input (plain) is 100

			byte [] cipher = crypt.doFinal(plain);

			log.debug("length of cipher is " + cipher.length);

			return cipher;
		}
		return null;
	}

	@Override
	public byte[]  receive() {
		byte[] message=null;
		byte[] originmessage=null;
		try {

			message = decoratedChannel.receive();
			originmessage = message;
			if(message!=null){
				String temp = new String (message);

				if (!(temp.startsWith("!list") || temp.startsWith("!end") || temp.startsWith("!exit")))
					return decrypt(message);
				else 
					return message;
			}

		} catch (InvalidKeyException e) {
			log.debug("Error: InvalidKey");
			return originmessage;
		} catch (IllegalBlockSizeException e) {
			log.debug("Error: IllegalBlockSizeException");
			return originmessage;
		} catch (BadPaddingException e) {
			log.debug("Error: BadPaddingException");
			return originmessage;
		} catch (UnsupportedEncodingException e) {
			log.debug("Error: UnsupportedEncodingException");
			return originmessage;
		}
		return null;
	}

	private byte[]  decrypt(byte[]  message) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UnsupportedEncodingException {


		// Now decrypt: length of input(cipher) is 128;

		try {
			crypt = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding");
		} catch (NoSuchAlgorithmException e) {
			log.debug("Error: "+e.getMessage());
		} catch (NoSuchPaddingException e) {
			log.debug("Error: "+e.getMessage());
		} 
		crypt.init(Cipher.DECRYPT_MODE, this.getPrivateKey());

		if (message!=null){
			byte [] decrypted_cipher = crypt.doFinal(message);

			log.debug("length of decrypted cipher is " + decrypted_cipher.length);

			return decrypted_cipher;
		}

		return null;

	}

	public PublicKey getPublicKey() {

		return decoratedChannel.getPublicKey();
	}
	public void setPublicKey(PublicKey publicKey) {
		decoratedChannel.setPublicKey(publicKey);
	}
	public  PrivateKey getPrivateKey() {
		return decoratedChannel.getPrivateKey();
	}
	public  void setPrivateKey(PrivateKey privateKey) {
		decoratedChannel.setPrivateKey(privateKey);
	}

	public SecretKey getSecretKey() {
		return decoratedChannel.getSecretKey();
	}

	public void setSecretKey(SecretKey secretKey) {
		this.decoratedChannel.setSecretKey(secretKey);
	}

	public IvParameterSpec getIvParameter() {
		return decoratedChannel.getIvParameter();
	}

	public void setIvParameter(IvParameterSpec ivParameter) {
		this.decoratedChannel.setIvParameter(ivParameter);
	}
	public void closeSocket(){
		decoratedChannel.closeSocket();
	}
}
